// Copyright 2011 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

function MapEditor(mapDiv, panel, list,
                   store, baseMap, latitude, longitude, zoom) {
  this.mapDiv = mapDiv;
  this.panel = panel;
  this.list = list;
  this.store = store;
  this.models = {};
  this.views = {};
  this.lastSyncTime = 0;
  this.pendingSelection = window.location.hash.replace('#', '');
  this.labelsVisible = false;

  var size = new google.maps.Size(17, 17);
  var anchor = new google.maps.Point(8, 8);
  this.POINT = new google.maps.MarkerImage('dot-17.png', size, null, anchor);
  this.POINT_HIGHLIGHTED =
      new google.maps.MarkerImage('dot-glow-17.png', size, null, anchor);
  this.GHOST = new google.maps.MarkerImage('ring-17.png', size, null, anchor);
  this.GHOST_HIGHLIGHTED =
      new google.maps.MarkerImage('ring-glow-17.png', size, null, anchor);

  // Set up the map view.

  this.map = new google.maps.Map(mapDiv, {
    center: new google.maps.LatLng(latitude, longitude),
    zoom: zoom,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    draggableCursor: 'crosshair',
    scaleControl: true,
    scaleControlOptions: {position: google.maps.ControlPosition.BOTTOM},
    mapTypeControl: false,
    streetViewControl: false,
    panControl: false,
    zoomControlOptions: {position: google.maps.ControlPosition.TOP_RIGHT}
  });
  this.map.mapTypes.set('base', baseMap);
  this.map.setMapTypeId('base');
  listen(this.map, 'click', this.onMapClick, this);

  // Create an overlay, just to get the projection.

  this.overlay = new google.maps.OverlayView();
  this.overlay.draw = function() {};
  this.overlay.setMap(this.map);

  // Set up the panel.

  // this.addButton = create(panelDiv, 'button', 'add', 'Add a label');
  this.labelCheckbox = create(panel, 'input', '', '', {type: 'checkbox'});
  this.labelCheckbox.id = 'showLabels';
  this.labelCheckbox.checked = this.labelsVisible;
  listen(this.labelCheckbox, 'click',
    function() { this.setLabelsVisible(this.labelCheckbox.checked); }, this);
  create(panel, 'label', '', 'Show labels on map', {'for': 'showLabels'});
  this.searchInput = create(panel, 'input', 'search');
  listen(this.searchInput, 'keyup', this.onSearchEdited, this);
  listen(this.searchInput, 'paste', this.onSearchEdited, this);
  listen(this.searchInput, 'cut', this.onSearchEdited, this);

  // Set up the info bubble.

  var content = create(null, 'div', 'bubble');
  this.titleInput = create(content, 'input');

  var actionDiv = create(content, 'div', 'action');
  this.saveButton = create(actionDiv, 'button', 'save', 'Save');
  listen(this.saveButton, 'click', this.onSaveClick, this);
  this.deleteButton = create(actionDiv, 'button', 'delete', 'Delete');
  listen(this.deleteButton, 'click', this.onDeleteClick, this);
  this.featureLink = create(actionDiv, 'a', 'link', 'Link to this point');
  hide(this.saveButton);

  this.bubble = new google.maps.InfoWindow({content: content});
  listen(this.bubble, 'closeclick', this.onBubbleClose, this);

  listen(this.bubble, 'domready', this.titleInput.focus, this.titleInput);
  listen(this.titleInput, 'keyup', this.onTitleEdited, this);
  listen(this.titleInput, 'paste', this.onTitleEdited, this);
  listen(this.titleInput, 'cut', this.onTitleEdited, this);

  // Initialize state.

  this.selectedFid = null;
  this.highlightedFid = null;
  this.highlightedHistory = null;

  // Start synchronizing data.

  this.sync();
}

MapEditor.prototype.sync = function() {
  this.store.sync(this.lastSyncTime, this.onStoreSync, this);
  window.setTimeout(bind(this.sync, this), 10000);  // update every 10 sec
};

MapEditor.prototype.select = function(fid) {
  if (fid !== this.selectedFid) {
    this.deselect();
    if (fid in this.models) {
      this.featureLink.href = '#' + fid;
      this.titleInput.value = this.models[fid].title || '';
      this.bubble.open(this.map, this.views[fid].marker);
      this.views[fid].marker.setDraggable(true);
      addClass(this.views[fid].item, 'selected');
      show(this.views[fid].expand);
      this.selectedFid = fid;
    }
  }
};

MapEditor.prototype.deselect = function() {
  if (this.selectedFid) {
    var fid = this.selectedFid;
    this.bubble.close();
    this.views[fid].marker.setDraggable(false);
    removeClass(this.views[fid].item, 'selected');
    hide(this.views[fid].expand);
    this.hideHistory();
    this.selectedFid = null;
    if (fid === this.tentativeFid) {
      this.unloadFeature(this.tentativeFid);
      this.tentativeFid = null;
    }
  }
};

MapEditor.prototype.showHistory = function(fid, entries) {
  this.hideHistory();
  this.historyModels = [];
  this.historyViews = [];
  if (!this.views[fid].history) {
    this.views[fid].history = create(this.views[fid].item, 'ul', 'history');
  }
  var nowSeconds = (new Date()).getTime() / 1000;
  for (var i = 0; i < entries.length; i++) {
    var time = describeAge(nowSeconds - entries[i][0]);
    var model = entries[i][1];
    if (model) {
      var index = this.historyModels.length;
      var item = create(this.views[fid].history, 'li', '');
      var title = create(item, 'span', 'title', time + ' ');
      var revert = create(item, 'a', 'revert', '(revert)', {href: '#'});
      listen(revert, 'click', bind(this.onHistoryRevert, this, index));
      listen(item, 'mouseover', bind(this.onHistoryOver, this, index));
      listen(item, 'mouseout', bind(this.onHistoryOut, this, index));
      listen(item, 'click', bind(this.onHistoryClick, this, index));
      var marker = new google.maps.Marker({
        map: this.map,
        icon: this.GHOST,
        position: new google.maps.LatLng(model.latitude, model.longitude),
        draggable: false
      });
      listen(marker, 'mouseover', bind(this.onHistoryOver, this, index));
      listen(marker, 'mouseout', bind(this.onHistoryOut, this, index));
      this.historyModels.push(model);
      this.historyViews.push({marker: marker, item: item, title: title});
    }
  }
};

MapEditor.prototype.hideHistory = function() {
  if (this.historyModels) {
    this.unhighlightHistory();
    for (var i = 0; i < this.historyViews.length; i++) {
      this.historyViews[i].marker.setMap(null);
    }
    clear(this.views[this.selectedFid].history);
    this.historyModels = null;
    this.historyViews = null;
  }
};

MapEditor.prototype.highlightHistory = function(index) {
  this.unhighlightHistory();
  this.historyViews[index].marker.setIcon(this.GHOST_HIGHLIGHTED);
  this.historyViews[index].marker.setZIndex(1);
  addClass(this.historyViews[index].title, 'highlighted');
  this.highlightedHistory = index;
};

MapEditor.prototype.unhighlightHistory = function() {
  if (this.highlightedHistory != null) {
    var index = this.highlightedHistory;
    this.historyViews[index].marker.setIcon(this.GHOST);
    this.historyViews[index].marker.setZIndex(0);
    removeClass(this.historyViews[index].title, 'highlighted');
    this.highlightedHistory = null;
  }
};

MapEditor.prototype.highlight = function(fid) {
  this.unhighlight();
  this.views[fid].marker.setIcon(this.POINT_HIGHLIGHTED);
  this.views[fid].marker.setZIndex(1);
  if (this.views[fid].label) {
    this.views[fid].label.highlight();
  }
  addClass(this.views[fid].title, 'highlighted');
  this.highlightedFid = fid;
};

MapEditor.prototype.unhighlight = function() {
  if (this.highlightedFid) {
    var fid = this.highlightedFid;
    if (this.views[fid]) {
      this.views[fid].marker.setIcon(this.POINT);
      this.views[fid].marker.setZIndex(0);
      if (this.views[fid].label) {
        this.views[fid].label.unhighlight();
      }
      removeClass(this.views[fid].title, 'highlighted');
    }
    this.highlightedFid = null;
  }
};

MapEditor.prototype.startEditing = function() {
  this.titleInput.value = this.models[this.selectedFid].title || '';
  show(this.saveButton);
};

MapEditor.prototype.saveEdit = function() {
  if (this.selectedFid === this.tentativeFid) {
    this.tentativeFid = null;
  }
  var model = this.models[this.selectedFid];
  model.title = this.titleInput.value;
  this.loadFeature(this.selectedFid, model);
  this.updateItem(this.selectedFid);
  this.store.put(this.selectedFid, model, this.onStorePut, this);
};

function Label(parent, marker, map, overlay) {
  this.marker = marker;
  this.overlay = overlay;  // just to get the projection
  this.div = create(parent, 'div', 'label');
  listen(marker, 'title_changed', this.update, this);
  listen(marker, 'position_changed', this.update, this);
  listen(map, 'bounds_changed', this.update, this);
};

Label.MAX_CHARS = 32;

Label.prototype.highlight = function() {
  addClass(this.div, 'highlighted');
  this.div.style.zIndex = 3;
};

Label.prototype.unhighlight = function() {
  removeClass(this.div, 'highlighted');
  this.div.style.zIndex = 2;
};

Label.prototype.setVisible = function(visible) {
  this.visible = visible;
  if (visible) {
    this.div.style.visibility = 'visible';
    this.update();
  } else {
    this.div.style.visibility = 'hidden';
  }
};

Label.prototype.update = function() {
  if (this.visible) {
    var text = this.marker.getTitle() || '';
    if (text.length > Label.MAX_CHARS) {
      text = text.substring(0, Label.MAX_CHARS) + '...';
    }
    setText(this.div, text);
 
    var projection = this.overlay.getProjection();
    var xy = projection.fromLatLngToContainerPixel(this.marker.getPosition());
    this.div.style.left = (xy.x - this.div.offsetWidth/2) + 'px';
    this.div.style.top = (xy.y - this.div.offsetHeight - 4) + 'px';
  }
};

MapEditor.prototype.setLabelsVisible = function(visible) {
  this.labelsVisible = visible;
  for (var fid in this.views) {
    if (this.views[fid].label) {
      this.views[fid].label.setVisible(visible);
    }
  }
};

MapEditor.prototype.loadFeature = function(fid, model) {
  if (model) {
    if (fid in this.models) {
      this.views[fid].marker.setPosition(
          new google.maps.LatLng(model.latitude, model.longitude));
      this.views[fid].marker.setTitle(model.title);
    } else {
      var marker = new google.maps.Marker({
        map: this.map,
        icon: this.POINT,
        position: new google.maps.LatLng(model.latitude, model.longitude),
        title: model.title,
        draggable: false
      });
      listen(marker, 'click', bind(this.select, this, fid));
      listen(marker, 'dragend', bind(this.onMarkerDragEnd, this, fid));
      listen(marker, 'mouseover', bind(this.onItemOver, this, fid));
      listen(marker, 'mouseout', bind(this.onItemOut, this, fid));
      var label = new Label(this.mapDiv, marker, this.map, this.overlay);
      label.setVisible(this.labelsVisible);
      this.views[fid] = {marker: marker, label: label};
    }
    this.models[fid] = model;
    if (fid !== this.tentativeFid) {
      this.updateItem(fid);
    }
  } else {
    this.onStoreDelete(fid);
  }
  this.onSearchEdited();  // update visibility according to the search filter
};

MapEditor.prototype.unloadFeature = function(fid) {
  if (fid === this.selectedFid) {
    this.deselect();
  }
  if (fid in this.views) {
    this.views[fid].marker.setMap(null);  // also closes attached InfoWindow
    if (this.views[fid].label) {
      this.views[fid].label.setVisible(false);
    }
    if (this.views[fid].title) {
      remove(this.views[fid].title);
    }
    if (this.views[fid].item) {
      remove(this.views[fid].item);
    }
    delete this.views[fid];
  }
  delete this.models[fid];
};

MapEditor.prototype.normalizeTitle = function(title) {
  var key = title || '';
  key = key.toLowerCase();
  key = key.replace(/ +/g, ' ');
  key = key.replace(/^ */, '');
  key = key.replace(/^the */, '');
  return key;
};

MapEditor.prototype.updateItem = function(fid) {
  if (!this.views[fid].item) {
    var item = create(null, 'li');
    listen(item, 'click', bind(this.onItemClick, this, fid));
    listen(item, 'mouseover', bind(this.onItemOver, this, fid));
    listen(item, 'mouseout', bind(this.onItemOut, this, fid));
    this.views[fid].item = item;
    this.views[fid].expand = create(item, 'span', 'expand', '\u25bc');
    this.views[fid].title = create(item, 'span', 'title');
    listen(this.views[fid].expand, 'click', this.onExpandClick, this, fid);
    hide(this.views[fid].expand);
  }
  setText(this.views[fid].title, this.models[fid].title);
  var title = this.normalizeTitle(this.models[fid].title);
  var closestFid = null;
  var closestTitle = null;
  for (var otherFid in this.models) {
    var otherTitle = this.normalizeTitle(this.models[otherFid].title);
    if (otherTitle > title) {
      if (closestFid === null || otherTitle < closestTitle) {
        closestFid = otherFid;
        closestTitle = otherTitle;
      }
    }
  }
  if (closestFid === null) {
    this.list.appendChild(this.views[fid].item);
  } else {
    this.list.insertBefore(this.views[fid].item, this.views[closestFid].item);
  }
};

// Handlers for UI events.

MapEditor.prototype.onMapClick = function(event) {
  this.deselect();
  var fid = makeId();
  this.tentativeFid = fid;
  this.loadFeature(fid, {
    latitude: event.latLng.lat(),
    longitude: event.latLng.lng()
  });
  this.select(fid);
  this.startEditing();
};

MapEditor.prototype.onMarkerDragEnd = function(fid) {
  var position = this.views[fid].marker.getPosition();
  var model = this.models[fid];
  model.latitude = position.lat();
  model.longitude = position.lng();
  if (fid !== this.tentativeFid) {
    this.store.put(fid, model);
  }
};

MapEditor.prototype.onSearchEdited = function(event) {
  var query = this.normalizeTitle(this.searchInput.value);
  for (var fid in this.views) {
    var item = this.views[fid].item;
    if (item) {
      var title = this.normalizeTitle(this.models[fid].title);
      item.style.display = (title.indexOf(query) > -1) ? '' : 'none';
    }
  }
};

MapEditor.prototype.onTitleEdited = function(event) {
  show(this.saveButton);
  if (event.keyCode === 13) {
    this.saveEdit();
  }
};

MapEditor.prototype.onSaveClick = function(event) {
  this.saveEdit();
  event.stopPropagation();
};

MapEditor.prototype.onDeleteClick = function(event) {
  if (this.selectedFid === this.tentativeFid) {
    this.unloadFeature(this.selectedFid);
    this.tentativeFid = null;
  } else {
    this.store.put(this.selectedFid, null, this.onStoreDelete, this);
  }
  event.stopPropagation();
};

MapEditor.prototype.onBubbleClose = function(event) {
  this.deselect();
};

MapEditor.prototype.onItemClick = function(fid) {
  this.select(fid);
};

MapEditor.prototype.onItemOver = function(fid) {
  this.highlight(fid);
};

MapEditor.prototype.onItemOut = function(fid) {
  if (this.highlightedFid == fid) {
    this.unhighlight();
  }
};

MapEditor.prototype.onExpandClick = function() {
  if (this.historyModels) {
    this.hideHistory();
  } else {
    this.store.history(this.selectedFid, this.onStoreHistory, this);
  }
};

MapEditor.prototype.onHistoryClick = function(index) {
  if (this.historyModels) {
    var marker = this.historyViews[index].marker;
    var position = marker.getPosition();
    var bounds = new google.maps.LatLngBounds(position, position);
    this.map.panToBounds(bounds);
    marker.setZIndex(1);
    marker.setAnimation(google.maps.Animation.BOUNCE);
    window.setTimeout(
        function() { marker.setZIndex(0); marker.setAnimation(null); }, 2000);
  }
};

MapEditor.prototype.onHistoryOver = function(index) {
  this.highlightHistory(index);
};

MapEditor.prototype.onHistoryOut = function(index) {
  if (this.highlightedHistory === index) {
    this.unhighlightHistory();
  }
};

MapEditor.prototype.onHistoryRevert = function(index) {
  var fid = this.selectedFid;
  this.loadFeature(this.selectedFid, this.historyModels[index]);
  this.hideHistory();
  this.store.put(this.selectedFid, this.models[this.selectedFid]);
  this.deselect();
  this.select(this.selectedFid);  // reopen bubble with updated title
};

// Handlers called when store requests are completed.

MapEditor.prototype.onStoreSync = function(result) {
  this.lastSyncTime = result[0];
  var models = result[1];
  for (var fid in models) {
    this.loadFeature(fid, models[fid]); 
  }
  if (this.pendingSelection) {
    this.select(this.pendingSelection);
    this.pendingSelection = null;
  }
};

MapEditor.prototype.onStorePut = function() {
  hide(this.saveButton);
};

MapEditor.prototype.onStoreDelete = function(fid) {
  this.unloadFeature(fid);
};

MapEditor.prototype.onStoreHistory = function(data) {
  this.showHistory(data[0], data[1]);
};

/*
function Marker(map, latitude, longitude) {
  this.setMap(map);
  this.input = document.createElement('input');
  this.input.style.display = 'none';
}

Marker.prototype = new google.maps.OverlayView();

Marker.prototype.onAdd = function() {
  this.getPanes().overlayLayer.appendChild(this.input);
  this.input.style.display = '';
};

Marker.prototype.draw = function() {
};

Marker.prototype.onRemove = function() {
  this.input.style.display = 'none';
};
*/
